/**
 * 文件名称: serial.c
 * 摘    要: linux应用程序下操作serial源文件
 * 来    源: 自我总结
 *
 * 当前版本: 1.0 
 * 作    者: huenrong
 * 完成日期: 2019年05月15日
 **/

#include "serial.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <pthread.h>

static char g_serial_prefix[12] = "/dev/ttymxc";    // 串口名前缀(这里需要根据实际开发板更改)

int g_serial_fd[MAX_SERIAL_NUM] = {-1};             // 记录所有串口的文件描述符
int g_serial_receive_buf_len[MAX_SERIAL_NUM] = {0};     // 串口接收线程接收到的数据长度
char g_serial_receive_buf[MAX_SERIAL_NUM][RECEIVE_BUF_LEN] = {0};  // 串口接收线程接收到的数据

// 延时，大于1ms
static int mysleep(const int isleep)
{
    int t;

    if (isleep > 1000)
    {
        t = 1000;
    }
    else 
    {
        t = isleep;
    }

    usleep(t);                  //等待t微秒
    
    return (isleep-t);
}

/************************************************************************
函数名称: open_serial
函数功能: 打开串口并设置属性
函数参数: port: 串口号
          speed: 波特率, 默认为115200
          databit: 数据位, 默认为8位数据位
          parity: 奇偶检验位, 默认为无校验'n'或'N'
          stopbit: 停止位, 默认为1位停止位
函数返回: 成功: 返回0
            失败: 返回-1
************************************************************************/
int open_serial(const int port, const int speed, const int databit, \
                const int parity, const int stopbit)
{
    char serial_name[15] = {0};     // 记录开发板串口名
    struct termios option = {0};    // 串口属性结构体
    speed_t set_speed = 0;          // 最终设置的波特率
    int fd = -1;

    if ((port < 0) || (port >= MAX_SERIAL_NUM))     // 判断串口号是否符合要求
    {
        printf("no serial: %s%d!\n", g_serial_prefix, port);
        return -1;
    }

    if (g_serial_fd[port] > 0)      // 判断串口是否已经被打开
    {
        printf("serial: %s%d is already open!\n", g_serial_prefix, port);
        return -1;
    }

    sprintf(serial_name, "%s%d", g_serial_prefix, port);        // 记录串口全名
    fd = open(serial_name, O_RDWR | O_NOCTTY | O_NDELAY);       // 打开串口
    if (fd < 0)
    {
        printf("serial: %s%d is open error! \n", g_serial_prefix, port);
        close(fd);
        return -1;
    }

    tcgetattr(fd, &option);        // 获取终端属性

    // 设置波特率
    switch (speed)
    {
        case 460800: set_speed = B460800; break;
        case 115200: set_speed = B115200; break;
        case 57600: set_speed = B57600; break;
        case 38400: set_speed = B38400; break;
        case 19200: set_speed = B19200; break;
        case 9600: set_speed = B9600; break;
        case 4800: set_speed = B4800; break;
        case 2400: set_speed = B2400; break;
        default: set_speed = B115200; break;        // 默认波特率为115200
    }
    cfsetspeed(&option, set_speed);     // 设置输入波特率
    cfsetospeed(&option, set_speed);    // 设置输出波特率

    // 设置数据位
    switch (databit)
    {
        // 5位数据位
        case 5:
        {
            option.c_cflag &= ~CSIZE;
            option.c_cflag |= CS5;

            break;
        }

        // 6位数据位
        case 6:
        {
            option.c_cflag &= ~CSIZE;
            option.c_cflag |= CS6;

            break;
        }

        // 7位数据位
        case 7:
        {
            option.c_cflag &= ~CSIZE;
            option.c_cflag |= CS7;

            break;
        }

        // 8位数据位
        case 8:
        {
            option.c_cflag &= ~CSIZE;
            option.c_cflag |= CS8;

            break;
        }

        // 默认为8位数据位
        default:
        {
            option.c_cflag &= ~CSIZE;
            option.c_cflag |= CS8;

            break;
        }
    } // end of switch (databit)

    // 设置奇偶检验位
    switch (parity)
    {
        // 无校验
        case 'n':
        case 'N':
        {
            option.c_cflag &= ~PARENB;
            option.c_iflag &= ~INPCK;
            
            break;
        }

        // 奇校验
        case 'o':
        case 'O':
        {
            option.c_cflag |= (PARODD | PARENB);
            option.c_iflag |= INPCK;
            
            break;
        }

        // 偶校验
        case 'e':
        case 'E':
        {
            option.c_cflag |= PARENB;
            option.c_cflag &= ~PARODD;
            option.c_iflag |= INPCK;
            
            break;
        }

        // 默认为无校验
        default:
        {
            option.c_cflag &= ~PARENB;
            option.c_iflag &= ~INPCK;
            
            break;
        }
    }       // end of switch (parity)

    // 设置停止位
    switch (stopbit)
    {
        case 1: option.c_cflag &= ~CSTOPB; break;      // 1位停止位
        case 2: option.c_cflag |= CSTOPB; break;       // 2位停止位
        default: option.c_cflag &= ~CSTOPB; break;     // 默认为1位停止位
    }

    // 一般必设置的标志
    option.c_cflag |= (CLOCAL | CREAD);
    option.c_oflag &= ~(OPOST);
    option.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
    option.c_iflag &= ~(ICRNL | INLCR | IGNCR | IXON | IXOFF | IXANY);

    tcflush(fd, TCIOFLUSH);    // 处理未接收字符
    option.c_cc[VMIN] = 0;         // 设置最小接收字符
    option.c_cc[VTIME] = 0;        // 设置等待时间

    // 设置终端属性
    if (tcsetattr(fd, TCSANOW, &option) < 0)
    {
        return -1;
    }

    g_serial_fd[port] = fd;        // 在全局数组中记录打开的串口文件描述符
    printf("open serila name: %s \n", serial_name);

    return 0;
}

/************************************************************************
函数名称: close_serial
函数功能: 关闭串口
函数参数: port: 串口号
函数返回: 成功: 返回0
          失败: 返回-1
************************************************************************/
int close_serial(const int port)
{
    if (close(g_serial_fd[port]) < 0)       // 关闭串口
    {
        return -1;
    }
    g_serial_fd[port] = -1;     // 将全局变量中对应的串口文件描述符置为-1, 表示串口已经关闭

    return 0;
}

/************************************************************************
函数名称：SerialSendData
函数功能：串口发送数据
函数参数：port: 串口号
          send_buf: 需要发送的数据
          send_buf_len: 需要发送的数据长度
函数返回：成功: 返回实际发送的数据长度
          失败: 返回-1
************************************************************************/
int serial_send_data(const int port, const char *send_buf, const int send_buf_len)
{
    int ret = -1;

    ret = write(g_serial_fd[port], send_buf, send_buf_len);      // 向指定串口发送数据
    if (ret < 0)
    {
        printf("write data to serial failed!\n");

        return -1;
    }

    return ret;
}

/************************************************************************
函数名称: serial_receive_data
函数功能: 从串口接收指定长度的数据
函数参数: port: 串口号
          receive_buf: 串口接收到的数据
          receive_buf_len: 指定接收数据长度
函数返回: 成功: 返回实际接收的数据长度
          失败: 返回-1
************************************************************************/
int serial_receive_data(const int port, char *receive_buf, const int receive_buf_len)
{
    int ret = -1;

    ret = read(g_serial_fd[port], receive_buf, receive_buf_len);        // 接收串口数据
    if(ret < 0)
    {
        return -1;
    }

    return ret;
}

/************************************************************************
函数名称: serial_receive_onebyte
函数功能: 从串口接收1字节长度的数据
函数参数: port: 串口号
          receive_buf: 串口接收到的数据
          timeout: 超时时间(ms)
函数返回: 成功: 返回0
          失败: 返回-1
************************************************************************/
int serial_receive_onebyte(const int port, char *receive_buf, int timeout)
{
    int ret = -1;

    while(1)
    {
        ret = serial_receive_data(port, receive_buf, 1);
        if (1 == ret)
        {
            return 0;
        }
        else        // 接收失败，则延时1ms
        {
            if (0 == timeout)
            {
                return -1;
            }
            timeout = mysleep(timeout);
        }
    }

    return -1;
}

/************************************************************************
函数名称: serial_receive_pack
函数功能: 从串口接收数据包(带超时时间)
函数参数: port: 串口号
          receive_buf: 串口接收到的数据
          max_receive_len: 最大接收长度
          first_timeout: 第1个字节的超时时间
          timeout: 其余数据的间隔超时时间(ms)
函数返回: 成功: 返回实际接收数据长度
          失败: 返回-1
************************************************************************/
int serial_receive_pack(const int port, char *receive_buf, \
                        const int max_receive_len, int first_timeout, int timeout)
{
    int ret = -1;

    if (-1 == serial_receive_onebyte(port, receive_buf, first_timeout))
    {
        return -1;
    }

    while (1)
    {
        receive_buf++;
        ret++;
        if (-1 == serial_receive_onebyte(port, receive_buf, timeout))
        {
            return ret;
        }
        else if (max_receive_len == ret)        // 当前接收数据长度达到最大接收长度，直接返回
        {
            return ret;
        }
    }

    return ret;
}

/************************************************************************
函数名称: creat_serial_receive_thread
函数功能: 创建串口接收线程
函数参数: port: 串口号
函数返回: 无
************************************************************************/
static void serial_receive_thread(const void *port)
{
    int ret = -1;
    int receive_port = 0;
    char receive_data = 0;      // 串口接收数据

    receive_port = (int)port;

    while (1)
    {
        ret = serial_receive_data(receive_port, &receive_data, 1);
        if (1 == ret)
        {
            g_serial_receive_buf[receive_port][g_serial_receive_buf_len[receive_port]++] = receive_data;
        }
    }
}

/************************************************************************
函数名称: creat_serial_receive_thread
函数功能: 创建串口接收线程(类似于单片机的串口中断)
函数参数: port: 串口号
函数返回: 成功: 返回0
          失败: 返回-1
************************************************************************/
int creat_serial_receive_thread(const int port)
{
    int ret = -1;
    unsigned char fail_num = 0;     // 创建线程失败次数
    pthread_t serial_receive_thread_id = -1;        // 串口接收线程ID

    while (1)
    {
        // 创建串口接收线程
        ret = pthread_create(&serial_receive_thread_id, NULL, (void *)serial_receive_thread, (void *)port);
        if (0 != ret)
        {
            printf("creat serial_receive_thread fail!\n");
            usleep(50 * 1000);
            fail_num++;
            if (3 == fail_num)      // 创建3次都失败则认为失败
            {
                return -1;
            }
        }
        else if (0 == ret)      // 线程创建成功，分离线程，并退出
        {
            pthread_detach(serial_receive_thread_id);       // 线程分离，结束时自动回收资源
            return 0;
        }
    }
}



